package com.forester.foresterplatform.system.security;

import com.forester.foresterplatform.common.utils.JwtUtil;
import com.forester.foresterplatform.system.config.SecurityIgnoreUrl;
import com.forester.foresterplatform.system.exception.ForesterException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Stream;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    // 携带 token 的请求头
    public static final String AUTHORIZATION = "Authorization";


    @Autowired
    private SecurityIgnoreUrl securityIgnoreUrl;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // permit 接口无需认证直接放行: 比如 swagger 相关的接口
        Stream<AntPathRequestMatcher> permitAllUrlsStream = Arrays.stream(securityIgnoreUrl.getPermitAllUrls()).map(AntPathRequestMatcher::new);
        if(permitAllUrlsStream.anyMatch(matcher -> matcher.matches(request))) {
            filterChain.doFilter(request, response);
            return;
        }

        // 判断当前接口是否是 anonymous 接口
        boolean isAnonymous = false;
        Stream<AntPathRequestMatcher> anonymousUrlsStream = Arrays.stream(securityIgnoreUrl.getAnonymousUrls()).map(AntPathRequestMatcher::new);
        if(anonymousUrlsStream.anyMatch(matcher -> matcher.matches(request))) {
            isAnonymous = true;
        }

        // 认证接口 : 非匿名接口才需要强制认证给出认证信息, 匿名接口携带不合法的 token 算作正常行为
        String token = request.getHeader(AUTHORIZATION);
        try {
            if (!StringUtils.hasText(token) && !isAnonymous) {
                throw new ForesterException("token 不能为空");
            }

            if (JwtUtil.verify(token) && JwtUtil.isExpired(token) && !isAnonymous) {
                throw new ForesterException("token 过期");
            }

            if (!JwtUtil.verify(token) && !isAnonymous) {
                throw new ForesterException("token 不合法");
            }

            // 解析 username -> 数据库查询 -> 将 Authentication 放置到 SecurityContextHolder 中, 后续线程能直接获取, 则表示认证成功
            String username = JwtUtil.getUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);  // 用户名 或 密码错误会抛出 RuntimeException
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            // 非 anonymous 接口认证失败
            if(!isAnonymous)
                throw e;
        }

        // 认证成功 或 认证失败但是 anonymous 接口
        filterChain.doFilter(request, response);
    }
}
